#ifndef __IMATRIX3D_H__
#define __IMATRIX3D_H__

typedef enum MatrixConstructionFlag
{
	MCF_NOTHING		= 0,
	MCF_IDENTITY,
	MCF_COPY,
	MCF_FINAL		= 0xffffffff,
} MatrixConstructionFlag;

#include "CTypes.h"
#include <math.h>

template <class T>
class IMatrix3D
{
public:
	T m[16];
public:
	IMatrix3D(MatrixConstructionFlag Flag=MCF_NOTHING)
	{
		switch (Flag)
		{
			default:
			case MCF_NOTHING:
			{

			}
			break;

			case MCF_IDENTITY:
			{
				MakeIdentity();
			}
			break;
		}
	}

	IMatrix3D(MatrixConstructionFlag Flag,const IMatrix3D &other)
	{
		switch (Flag)
		{
			default:
			case MCF_NOTHING:
			{

			}
			break;

			case MCF_IDENTITY:
			{
				MakeIdentity();
			}
			break;

			case MCF_COPY:
			{
				for (u32 i=0;i<16;i++)
				{
					m[i] = other.m[i];
				}
			}
			break;

		}
	}

	virtual ~IMatrix3D()	{}

	inline void MakeIdentity()
	{
		m[0]=(T)1.0;m[1]=0;m[2]=0;m[3]=0;
		m[4]=0;m[5]=(T)1.0;m[6]=0;m[7]=0;
		m[8]=0;m[9]=0;m[10]=T(1.0);m[11]=0;
		m[12]=0;m[13]=0;m[14]=0;m[15]=T(1.0);
	}

	IMatrix3D<T> &SetTranslationFix(const vector3d<T> &translation)
	{
		m[12] = translation.X;
		m[13] = translation.Y;
		m[14] = translation.Z;
		return *this;
	}

	IMatrix3D<T> &SetTranslation(const vector3d<T> &translation)
	{
		m[12] = m[0]*translation.X + m[4]*translation.Y + m[8]*translation.Z + m[12];
		m[13] = m[1]*translation.X + m[5]*translation.Y + m[9]*translation.Z + m[13];
		m[14] = m[2]*translation.X + m[6]*translation.Y + m[10]*translation.Z + m[14];
		m[15] = m[3]*translation.X + m[7]*translation.Y + m[11]*translation.Z + m[15];

		return *this;
	}

	vector3d<T> GetTranslation()
	{
		return vector3d<T>(m[12],m[13],m[14]);
	}

	IMatrix3D<T> &SetScale(const vector3d<T>& scale)
	{
		m[0] *= scale.X;
		m[5] *= scale.Y;
		m[10] *= scale.Z;
		return *this;
	}

	vector3d<T> GetScale()
	{
		return vector3d<T>(m[0],m[5],m[10]);
	}

	inline IMatrix3D<T> &SetRotationDegrees(const vector3d<T>& rotation)
	{
		return SetRotationRadians(rotation*DEGTORAD);
	}

	inline IMatrix3D<T> &SetRotationRadians(const vector3d<T>& rotation)
	{
		const f64 cr = cos(rotation.X);
		const f64 sr = sin(rotation.X);
		const f64 cp = cos(rotation.Y);
		const f64 sp = sin(rotation.Y);
		const f64 cy = cos(rotation.Z);
		const f64 sy = sin(rotation.Z);

		m[0] = (T)(cp*cy);
		m[1] = (T)(cp*sy);
		m[2] = (T)(-sp);

		const f64 srsp = sr*sp;
		const f64 crsp = cr*sp;

		m[4] = (T)(srsp*cy-cr*sy);
		m[5] = (T)(srsp*sy+cr*cy);
		m[6] = (T)(sr*cp);

		m[8] = (T)(crsp*cy+sr*sy);
		m[9] = (T)(crsp*sy-sr*cy);
		m[10] = (T)(cr*cp);
		return *this;
	}

	inline IMatrix3D<T> &Multiply(const IMatrix3D<T>& m3,IMatrix3D<T>& m2)
	{
		m[0] = m3.m[0]*m2.m[0] + m3.m[4]*m2.m[1] + m3.m[8]*m2.m[2] + m3.m[12]*m2.m[3];
		m[1] = m3.m[1]*m2.m[0] + m3.m[5]*m2.m[1] + m3.m[9]*m2.m[2] + m3.m[13]*m2.m[3];
		m[2] = m3.m[2]*m2.m[0] + m3.m[6]*m2.m[1] + m3.m[10]*m2.m[2] + m3.m[14]*m2.m[3];
		m[3] = m3.m[3]*m2.m[0] + m3.m[7]*m2.m[1] + m3.m[11]*m2.m[2] + m3.m[15]*m2.m[3];

		m[4] = m3.m[0]*m2.m[4] + m3.m[4]*m2.m[5] + m3.m[8]*m2.m[6] + m3.m[12]*m2.m[7];
		m[5] = m3.m[1]*m2.m[4] + m3.m[5]*m2.m[5] + m3.m[9]*m2.m[6] + m3.m[13]*m2.m[7];
		m[6] = m3.m[2]*m2.m[4] + m3.m[6]*m2.m[5] + m3.m[10]*m2.m[6] + m3.m[14]*m2.m[7];
		m[7] = m3.m[3]*m2.m[4] + m3.m[7]*m2.m[5] + m3.m[11]*m2.m[6] + m3.m[15]*m2.m[7];

		m[8] = m3.m[0]*m2.m[8] + m3.m[4]*m2.m[9] + m3.m[8]*m2.m[10] + m3.m[12]*m2.m[11];
		m[9] = m3.m[1]*m2.m[8] + m3.m[5]*m2.m[9] + m3.m[9]*m2.m[10] + m3.m[13]*m2.m[11];
		m[10] = m3.m[2]*m2.m[8] + m3.m[6]*m2.m[9] + m3.m[10]*m2.m[10] + m3.m[14]*m2.m[11];
		m[11] = m3.m[3]*m2.m[8] + m3.m[7]*m2.m[9] + m3.m[11]*m2.m[10] + m3.m[15]*m2.m[11];

		m[12] = m3.m[0]*m2.m[12] + m3.m[4]*m2.m[13] + m3.m[8]*m2.m[14] + m3.m[12]*m2.m[15];
		m[13] = m3.m[1]*m2.m[12] + m3.m[5]*m2.m[13] + m3.m[9]*m2.m[14] + m3.m[13]*m2.m[15];
		m[14] = m3.m[2]*m2.m[12] + m3.m[6]*m2.m[13] + m3.m[10]*m2.m[14] + m3.m[14]*m2.m[15];
		m[15] = m3.m[3]*m2.m[12] + m3.m[7]*m2.m[13] + m3.m[11]*m2.m[14] + m3.m[15]*m2.m[15];

		return *this;
	}

	inline IMatrix3D<T> &operator*=(const IMatrix3D<T>& m2)
	{
		m[0] = m[0]*m2.m[0] + m[4]*m2.m[1] + m[8]*m2.m[2] + m[12]*m2.m[3];
		m[1] = m[1]*m2.m[0] + m[5]*m2.m[1] + m[9]*m2.m[2] + m[13]*m2.m[3];
		m[2] = m[2]*m2.m[0] + m[6]*m2.m[1] + m[10]*m2.m[2] + m[14]*m2.m[3];
		m[3] = m[3]*m2.m[0] + m[7]*m2.m[1] + m[11]*m2.m[2] + m[15]*m2.m[3];

		m[4] = m[0]*m2.m[4] + m[4]*m2.m[5] + m[8]*m2.m[6] + m[12]*m2.m[7];
		m[5] = m[1]*m2.m[4] + m[5]*m2.m[5] + m[9]*m2.m[6] + m[13]*m2.m[7];
		m[6] = m[2]*m2.m[4] + m[6]*m2.m[5] + m[10]*m2.m[6] + m[14]*m2.m[7];
		m[7] = m[3]*m2.m[4] + m[7]*m2.m[5] + m[11]*m2.m[6] + m[15]*m2.m[7];

		m[8] = m[0]*m2.m[8] + m[4]*m2.m[9] + m[8]*m2.m[10] + m[12]*m2.m[11];
		m[9] = m[1]*m2.m[8] + m[5]*m2.m[9] + m[9]*m2.m[10] + m[13]*m2.m[11];
		m[10] = m[2]*m2.m[8] + m[6]*m2.m[9] + m[10]*m2.m[10] + m[14]*m2.m[11];
		m[11] = m[3]*m2.m[8] + m[7]*m2.m[9] + m[11]*m2.m[10] + m[15]*m2.m[11];

		m[12] = m[0]*m2.m[12] + m[4]*m2.m[13] + m[8]*m2.m[14] + m[12]*m2.m[15];
		m[13] = m[1]*m2.m[12] + m[5]*m2.m[13] + m[9]*m2.m[14] + m[13]*m2.m[15];
		m[14] = m[2]*m2.m[12] + m[6]*m2.m[13] + m[10]*m2.m[14] + m[14]*m2.m[15];
		m[15] = m[3]*m2.m[12] + m[7]*m2.m[13] + m[11]*m2.m[14] + m[15]*m2.m[15];

		return *this;
	}

	inline IMatrix3D<T> &operator*=(T n)
	{
		for (u32 i=0;i<16;i++)
			m[i] *= n;

		return *this;
	}

	inline IMatrix3D<T> operator*(const IMatrix3D<T>& m2)
	{
		IMatrix3D<T> t(MCF_COPY,*this);
		IMatrix3D t2(MCF_IDENTITY);
		t2.m[0] = t.m[0]*m2.m[0] + t.m[4]*m2.m[1] + t.m[8]*m2.m[2] + t.m[12]*m2.m[3];
		t2.m[1] = t.m[1]*m2.m[0] + t.m[5]*m2.m[1] + t.m[9]*m2.m[2] + t.m[13]*m2.m[3];
		t2.m[2] = t.m[2]*m2.m[0] + t.m[6]*m2.m[1] + t.m[10]*m2.m[2] + t.m[14]*m2.m[3];
		t2.m[3] = t.m[3]*m2.m[0] + t.m[7]*m2.m[1] + t.m[11]*m2.m[2] + t.m[15]*m2.m[3];

		t2.m[4] = t.m[0]*m2.m[4] + t.m[4]*m2.m[5] + t.m[8]*m2.m[6] + t.m[12]*m2.m[7];
		t2.m[5] = t.m[1]*m2.m[4] + t.m[5]*m2.m[5] + t.m[9]*m2.m[6] + t.m[13]*m2.m[7];
		t2.m[6] = t.m[2]*m2.m[4] + t.m[6]*m2.m[5] + t.m[10]*m2.m[6] + t.m[14]*m2.m[7];
		t2.m[7] = t.m[3]*m2.m[4] + t.m[7]*m2.m[5] + t.m[11]*m2.m[6] + t.m[15]*m2.m[7];

		t2.m[8] = t.m[0]*m2.m[8] + t.m[4]*m2.m[9] + t.m[8]*m2.m[10] + t.m[12]*m2.m[11];
		t2.m[9] = t.m[1]*m2.m[8] + t.m[5]*m2.m[9] + t.m[9]*m2.m[10] + t.m[13]*m2.m[11];
		t2.m[10] = t.m[2]*m2.m[8] + t.m[6]*m2.m[9] + t.m[10]*m2.m[10] + t.m[14]*m2.m[11];
		t2.m[11] = t.m[3]*m2.m[8] + t.m[7]*m2.m[9] + t.m[11]*m2.m[10] + t.m[15]*m2.m[11];

		t2.m[12] = t.m[0]*m2.m[12] + t.m[4]*m2.m[13] + t.m[8]*m2.m[14] + t.m[12]*m2.m[15];
		t2.m[13] = t.m[1]*m2.m[12] + t.m[5]*m2.m[13] + t.m[9]*m2.m[14] + t.m[13]*m2.m[15];
		t2.m[14] = t.m[2]*m2.m[12] + t.m[6]*m2.m[13] + t.m[10]*m2.m[14] + t.m[14]*m2.m[15];
		t2.m[15] = t.m[3]*m2.m[12] + t.m[7]*m2.m[13] + t.m[11]*m2.m[14] + t.m[15]*m2.m[15];

		return t2;
	}

	inline vector3d<T> operator*(const vector3d<T>& m2)
	{
		IMatrix3D<T> t(MCF_IDENTITY);
		t.SetTranslation(m2);
//		IMatrix3D t2(MCF_COPY,this->operator *(t));
		IMatrix3D t2(MCF_COPY,this->operator *(t));
		return t2.GetTranslation();
	}

	inline IMatrix3D<T> &operator=(const IMatrix3D<T>& m2)
	{
		memcpy(m,m2.m,sizeof(T)*16);
		return *this;
	}

	inline bool operator==(const IMatrix3D<T>& m2)
	{
		return !(memcmp(m,m2.m,sizeof(T)*16));
	}

};

typedef IMatrix3D<f32>	matrix3df32;
typedef IMatrix3D<vPos>	matrix3dvpos;

#endif
